package com.alvinlkk.basic.chapter21;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

class Toast {
    public enum Status {DRY, BUTTERED, JAMMED }
    
    private Status status = Status.DRY;
    
    private final int id;
    
    public Toast(int idn) {
        id = idn;
    }
    
    public void butter() {
        status = Status.BUTTERED;
    }
    
    public void jam() {
        status = Status.JAMMED;
    }
    
    public Status getStatus() {
        return status;
    }
    
    public int getId() {
        return id;
    }
    
    @Override
    public String toString() {
        return "Toast " + id + ": " + status;
    }
}

class ToastQueue extends LinkedBlockingQueue<Toast> {}

class Toaster implements Runnable {
    
    private ToastQueue toastQueue;
    
    private int count = 0;
    
    private Random rand = new Random(47);
    
    public Toaster(ToastQueue tq) {
        toastQueue = tq;
    }
    
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                TimeUnit.MILLISECONDS.sleep(100 + rand.nextInt(500));
                Toast t = new Toast(count ++);
                System.out.println(t);
                toastQueue.put(t);
            }  
        } catch (Exception e) {
            System.out.println("Toaster interrutped");
        }
        System.out.println("Toaster off");
    }
}

class Butterer implements Runnable {
    
    private ToastQueue dryQueue, butteredQueue;
    
    public Butterer(ToastQueue dryQueue, ToastQueue butteredQueue) {
        this.dryQueue = dryQueue;
        this.butteredQueue = butteredQueue;
    }
    
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                Toast toast = dryQueue.take();
                toast.butter();
                System.out.println(toast);
                butteredQueue.put(toast);
            }  
        } catch (Exception e) {
            System.out.println("Butterer interrutped");
        }
        System.out.println("Butterer off");
    }
}

class Jammer implements Runnable {
    
    private ToastQueue butterQueue, finishedQueue;
    
    public Jammer(ToastQueue butterQueue, ToastQueue finishedQueue) {
        this.butterQueue = butterQueue;
        this.finishedQueue = finishedQueue;
    }
    
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                Toast toast = butterQueue.take();
                toast.jam();
                System.out.println(toast);
                finishedQueue.put(toast);
            }  
        } catch (Exception e) {
            System.out.println("Jammer interrutped");
        }
        System.out.println("Jammer off");
    }
}

class Eater implements Runnable {
    
    private ToastQueue finishedQueue;
    
    private int count = 0;
    
    public Eater(ToastQueue tq) {
        finishedQueue = tq;
    }
    
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                Toast toast = finishedQueue.take();
                if(toast.getId() != count ++ || toast.getStatus() != Toast.Status.JAMMED) {
                    System.out.println(">>>>> Error: " + toast);
                    System.exit(1);
                } else {
                    System.out.println("Chomp! " + toast);
                }
            }  
        } catch (Exception e) {
            System.out.println("Eater interrutped");
        }
      System.out.println("Eater off");
    }
}

public class ToastOMatic {
    public static void main(String[] args) throws InterruptedException {
        ToastQueue dryQueue = new ToastQueue(),
                   butteredQueue = new ToastQueue(),
                   finishedQueue = new ToastQueue();
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new Toaster(dryQueue));
        exec.execute(new Butterer(dryQueue, butteredQueue));
        exec.execute(new Jammer(butteredQueue, finishedQueue));
        exec.execute(new Eater(finishedQueue));
        TimeUnit.SECONDS.sleep(5);
        exec.shutdownNow();
    }
}
